package com.carnation.class_loader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.WritableByteChannel;

/**
 * <p>
 * 自定义类加载器
 * CustomClassLoader：自定义ClassLoader加载自定义位置的.class文件。
 *
 * 使用场景：
 * 1：加密：Java代码可以轻易的被反编译，如果你需要把自己的代码进行加密以防止反编译，可以先将编译后的代码用某种加密算法加密，类加密后就不能再用Java的ClassLoader去加载类了，
 * 这时就需要自定义ClassLoader在加载类的时候先解密类，然后再加载。
 * 2：从非标准的来源加载代码：如果你的字节码是放在数据库、甚至是在云端，就可以自定义类加载器，从指定的来源加载类。
 * 1、2的综合运用：比如你的应用需要通过网络来传输Java类的字节码，为了安全性，这些字节码经过了加密处理，这个时候你就需要自定义类加载器来从某个网络地址上读取加密后的字节代码（findClass()），
 * 接着进行解密和验证，最后定义（defineClass()）出在Java虚拟机中运行的类。
 * </p>
 *
 * @author: Sunny
 * @date: 2021/1/29
 * @version: v1.0.0
 */
public class CustomClassLoader extends ClassLoader {

    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        CustomClassLoader loader = new CustomClassLoader();
        Class clazz = Class.forName("com.carnation.class_loader.Hello", true, loader);
        Object o = clazz.newInstance();
        Method method = clazz.getMethod("hello", null);
        method.invoke(o);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        File file = getClassFile(name);
        try {
            byte[] bytes = getClassBytes(file);
            // 可以在此处做class文件的解密处理，用于解决敏感class文件的防反编译处理
            Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
            return c;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }

    private File getClassFile(String name) {
        File file = new File(Thread.currentThread().getContextClassLoader().getResource("").getPath() + "test/Hello.class");
        return file;
    }

    private byte[] getClassBytes(File file) throws Exception {
        FileInputStream fis = new FileInputStream(file);
        FileChannel fc = fis.getChannel();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        WritableByteChannel wbc = Channels.newChannel(baos);
        ByteBuffer by = ByteBuffer.allocate(1024);

        while (true) {
            int i = fc.read(by);
            if (i == 0 || i == -1) {
                break;
            }
            by.flip();
            wbc.write(by);
            by.clear();
        }
        fis.close();
        return baos.toByteArray();
    }

}
